package cn.assumejson.springframework.beans.context.support;

import cn.assumejson.springframework.aop.*;
import cn.assumejson.springframework.aop.aspectj.annotation.InstantiationModelAwarePointcutAdvisorImpl;
import cn.hutool.aop.ProxyUtil;
import cn.hutool.core.lang.Assert;
import org.openjdk.jol.util.ClassUtils;

import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.*;

/**
 * @author gang.Li
 * @version 1.0
 * @since 1.0 2021/12/9 21:41
 */
public class AopUtils {

    public static List<Advisor> findAdvisorsThatCanApply(List<Advisor> candidateAdvisors, Class<?> clazz) {
        if (candidateAdvisors.isEmpty()) {
            return candidateAdvisors;
        }
        List<Advisor> eligibleAdvisors = new ArrayList<>();
        for (Advisor candidate : candidateAdvisors) {
            if (candidate instanceof InstantiationModelAwarePointcutAdvisorImpl && canApply(candidate, clazz)) {
                eligibleAdvisors.add(candidate);
            }
        }
        return eligibleAdvisors;
    }

    public static boolean canApply(Advisor advisor, Class<?> targetClass) {

        return canApply(advisor, targetClass, false);
    }

    public static boolean canApply(Advisor advisor, Class<?> targetClass, Boolean hasIntroductions) {
        if (advisor instanceof PointcutAdvisor) {
            PointcutAdvisor pca = (PointcutAdvisor) advisor;
            return canApply(pca.getPointcut(), targetClass, hasIntroductions);
        } else {
            return true;
        }
    }

    public static boolean canApply(Pointcut pc, Class<?> targetClass, Boolean hasIntroductions) {
        Assert.notNull(pc, "Pointcut must not be null");

        // 先判断类
        if (!pc.getClassFilter().matches(targetClass)) {
            return false;
        }

        // 判断方法
        MethodMatcher methodMatcher = pc.getMethodMatcher();
        if (methodMatcher == MethodMatcher.TRUE) {
            // No need to iterate the methods if we're matching any method anyway...
            return true;
        }
        // 转换类型
        IntroductionAwareMethodMatcher introductionAwareMethodMatcher = null;
        if (methodMatcher instanceof IntroductionAwareMethodMatcher) {
            introductionAwareMethodMatcher = (IntroductionAwareMethodMatcher) methodMatcher;
        }

        Set<Class<?>> classes = new LinkedHashSet<>();
        // 判断是否已经被JDK代理过
        if (!Proxy.isProxyClass(targetClass)) {
            classes.add(getUserClass(targetClass));
        }
        // cglib taget 是以父类的形式赋值的
        classes.addAll(getAllInterfacesForClassAsSet(targetClass, null));

        for (Class<?> aClass : classes) {
            Method[] declaredMethods = aClass.getDeclaredMethods();
            for (Method declaredMethod : declaredMethods) {
                if (declaredMethod != null ?
                        // 进行方法匹配
                        introductionAwareMethodMatcher.matches(declaredMethod, targetClass, hasIntroductions) :
                        methodMatcher.matches(declaredMethod, targetClass)) {
                    return true;
                }
            }
        }
        return false;
    }

    private static Collection<Class<?>> getAllInterfacesForClassAsSet(Class<?> clazz, ClassLoader classLoader) {
        Assert.notNull(clazz, "Class must not be null");
        if (clazz.isInterface() && isVisible(clazz, classLoader)) {
            return Collections.singleton(clazz);
        }
        Set<Class<?>> interfaces = new LinkedHashSet<>();
        Class<?> current = clazz;
        while (current != null) {
            Class<?>[] ifcs = current.getInterfaces();
            for (Class<?> ifc : ifcs) {
                if (isVisible(ifc, classLoader)) {
                    interfaces.add(ifc);
                }
            }
            current = current.getSuperclass();
        }
        return interfaces;
    }

    private static Class<?> getUserClass(Class<?> clazz) {

        if (clazz.getName().contains("$$")) {
            Class<?> superclass = clazz.getSuperclass();
            if (superclass != null && superclass != Object.class) {
                return superclass;
            }
        }
        return clazz;

    }

    public static boolean isVisible(Class<?> clazz, ClassLoader classLoader) {
        if (classLoader == null) {
            return true;
        }
        try {
            if (clazz.getClassLoader() == classLoader) {
                return true;
            }
        } catch (SecurityException ex) {
            // Fall through to loadable check below
        }

        // Visible if same Class can be loaded from given ClassLoader
        return isLoadable(clazz, classLoader);
    }

    private static boolean isLoadable(Class<?> clazz, ClassLoader classLoader) {
        try {
            return (clazz == classLoader.loadClass(clazz.getName()));
            // Else: different class with same name found
        } catch (ClassNotFoundException ex) {
            // No corresponding class found at all
            return false;
        }
    }


}
